"""
Base repository pattern implementation following SOLID principles.
"""

from typing import Any, Dict, List, Optional, Type, TypeVar, Generic, Union
from abc import ABC, abstractmethod
from sqlalchemy.orm import Session
from sqlalchemy import and_, or_, desc, asc, func
from sqlalchemy.exc import IntegrityError

from ..logging import get_logger
from .base import BaseModel

T = TypeVar('T', bound=BaseModel)


class IRepository(ABC, Generic[T]):
    """Repository interface."""
    
    @abstractmethod
    def create(self, obj_in: Dict[str, Any]) -> T:
        """Create a new record."""
        pass
    
    @abstractmethod
    def get_by_id(self, id: Any) -> Optional[T]:
        """Get record by ID."""
        pass
    
    @abstractmethod
    def get_all(self, skip: int = 0, limit: int = 100) -> List[T]:
        """Get all records."""
        pass
    
    @abstractmethod
    def update(self, id: Any, obj_in: Dict[str, Any]) -> Optional[T]:
        """Update record."""
        pass
    
    @abstractmethod
    def delete(self, id: Any) -> bool:
        """Delete record."""
        pass


class BaseRepository(IRepository[T]):
    """Base repository implementation."""
    
    def __init__(self, model: Type[T], db: Session):
        self.model = model
        self.db = db
        self.logger = get_logger(f"{model.__name__}Repository")
    
    def create(self, obj_in: Dict[str, Any]) -> T:
        """Create a new record."""
        try:
            db_obj = self.model(**obj_in)
            self.db.add(db_obj)
            self.db.commit()
            self.db.refresh(db_obj)
            
            self.logger.info(
                "Record created",
                model=self.model.__name__,
                id=db_obj.id
            )
            
            return db_obj
            
        except IntegrityError as exc:
            self.db.rollback()
            self.logger.error(
                "Integrity error creating record",
                model=self.model.__name__,
                error=str(exc),
                data=obj_in
            )
            raise
        except Exception as exc:
            self.db.rollback()
            self.logger.error(
                "Error creating record",
                model=self.model.__name__,
                error=str(exc),
                data=obj_in,
                exc_info=True
            )
            raise
    
    def get_by_id(self, id: Any) -> Optional[T]:
        """Get record by ID."""
        try:
            return self.db.query(self.model).filter(self.model.id == id).first()
        except Exception as exc:
            self.logger.error(
                "Error getting record by ID",
                model=self.model.__name__,
                id=id,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def get_by_ids(self, ids: List[Any]) -> List[T]:
        """Get records by IDs."""
        try:
            return self.db.query(self.model).filter(self.model.id.in_(ids)).all()
        except Exception as exc:
            self.logger.error(
                "Error getting records by IDs",
                model=self.model.__name__,
                ids=ids,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def get_all(self, skip: int = 0, limit: int = 100) -> List[T]:
        """Get all records."""
        try:
            return self.db.query(self.model).offset(skip).limit(limit).all()
        except Exception as exc:
            self.logger.error(
                "Error getting all records",
                model=self.model.__name__,
                skip=skip,
                limit=limit,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def get_active(self, skip: int = 0, limit: int = 100) -> List[T]:
        """Get active records only."""
        try:
            query = self.db.query(self.model)
            if hasattr(self.model, 'is_active'):
                query = query.filter(self.model.is_active == True)
            return query.offset(skip).limit(limit).all()
        except Exception as exc:
            self.logger.error(
                "Error getting active records",
                model=self.model.__name__,
                skip=skip,
                limit=limit,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def update(self, id: Any, obj_in: Dict[str, Any]) -> Optional[T]:
        """Update record."""
        try:
            db_obj = self.get_by_id(id)
            if not db_obj:
                return None
            
            # Update fields
            for field, value in obj_in.items():
                if hasattr(db_obj, field) and field != 'id':
                    setattr(db_obj, field, value)
            
            self.db.commit()
            self.db.refresh(db_obj)
            
            self.logger.info(
                "Record updated",
                model=self.model.__name__,
                id=id
            )
            
            return db_obj
            
        except IntegrityError as exc:
            self.db.rollback()
            self.logger.error(
                "Integrity error updating record",
                model=self.model.__name__,
                id=id,
                error=str(exc),
                data=obj_in
            )
            raise
        except Exception as exc:
            self.db.rollback()
            self.logger.error(
                "Error updating record",
                model=self.model.__name__,
                id=id,
                error=str(exc),
                data=obj_in,
                exc_info=True
            )
            raise
    
    def delete(self, id: Any) -> bool:
        """Delete record."""
        try:
            db_obj = self.get_by_id(id)
            if not db_obj:
                return False
            
            self.db.delete(db_obj)
            self.db.commit()
            
            self.logger.info(
                "Record deleted",
                model=self.model.__name__,
                id=id
            )
            
            return True
            
        except Exception as exc:
            self.db.rollback()
            self.logger.error(
                "Error deleting record",
                model=self.model.__name__,
                id=id,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def soft_delete(self, id: Any) -> bool:
        """Soft delete record."""
        try:
            db_obj = self.get_by_id(id)
            if not db_obj:
                return False
            
            if hasattr(db_obj, 'soft_delete'):
                db_obj.soft_delete()
                self.db.commit()
                
                self.logger.info(
                    "Record soft deleted",
                    model=self.model.__name__,
                    id=id
                )
                
                return True
            else:
                # Fallback to hard delete if soft delete not supported
                return self.delete(id)
                
        except Exception as exc:
            self.db.rollback()
            self.logger.error(
                "Error soft deleting record",
                model=self.model.__name__,
                id=id,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def restore(self, id: Any) -> bool:
        """Restore soft deleted record."""
        try:
            db_obj = self.db.query(self.model).filter(self.model.id == id).first()
            if not db_obj:
                return False
            
            if hasattr(db_obj, 'restore'):
                db_obj.restore()
                self.db.commit()
                
                self.logger.info(
                    "Record restored",
                    model=self.model.__name__,
                    id=id
                )
                
                return True
            
            return False
            
        except Exception as exc:
            self.db.rollback()
            self.logger.error(
                "Error restoring record",
                model=self.model.__name__,
                id=id,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def exists(self, id: Any) -> bool:
        """Check if record exists."""
        try:
            return self.db.query(self.model).filter(self.model.id == id).first() is not None
        except Exception as exc:
            self.logger.error(
                "Error checking record existence",
                model=self.model.__name__,
                id=id,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def count(self, filters: Optional[Dict[str, Any]] = None) -> int:
        """Count records."""
        try:
            query = self.db.query(self.model)
            
            if filters:
                query = self._apply_filters(query, filters)
            
            return query.count()
            
        except Exception as exc:
            self.logger.error(
                "Error counting records",
                model=self.model.__name__,
                filters=filters,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def find_by(self, **kwargs) -> List[T]:
        """Find records by criteria."""
        try:
            query = self.db.query(self.model)
            
            for key, value in kwargs.items():
                if hasattr(self.model, key):
                    query = query.filter(getattr(self.model, key) == value)
            
            return query.all()
            
        except Exception as exc:
            self.logger.error(
                "Error finding records",
                model=self.model.__name__,
                criteria=kwargs,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def find_one_by(self, **kwargs) -> Optional[T]:
        """Find one record by criteria."""
        try:
            query = self.db.query(self.model)
            
            for key, value in kwargs.items():
                if hasattr(self.model, key):
                    query = query.filter(getattr(self.model, key) == value)
            
            return query.first()
            
        except Exception as exc:
            self.logger.error(
                "Error finding record",
                model=self.model.__name__,
                criteria=kwargs,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def paginate(
        self,
        page: int = 1,
        size: int = 20,
        filters: Optional[Dict[str, Any]] = None,
        order_by: Optional[str] = None,
        order_desc: bool = False
    ) -> Dict[str, Any]:
        """Paginate records."""
        try:
            query = self.db.query(self.model)
            
            # Apply filters
            if filters:
                query = self._apply_filters(query, filters)
            
            # Apply ordering
            if order_by and hasattr(self.model, order_by):
                order_column = getattr(self.model, order_by)
                if order_desc:
                    query = query.order_by(desc(order_column))
                else:
                    query = query.order_by(asc(order_column))
            
            # Get total count
            total = query.count()
            
            # Apply pagination
            offset = (page - 1) * size
            items = query.offset(offset).limit(size).all()
            
            return {
                "items": items,
                "total": total,
                "page": page,
                "size": size,
                "pages": (total + size - 1) // size,
                "has_next": page * size < total,
                "has_prev": page > 1
            }
            
        except Exception as exc:
            self.logger.error(
                "Error paginating records",
                model=self.model.__name__,
                page=page,
                size=size,
                filters=filters,
                error=str(exc),
                exc_info=True
            )
            raise
    
    def _apply_filters(self, query, filters: Dict[str, Any]):
        """Apply filters to query."""
        for key, value in filters.items():
            if hasattr(self.model, key):
                column = getattr(self.model, key)
                
                if isinstance(value, dict):
                    # Handle complex filters
                    for op, val in value.items():
                        if op == 'eq':
                            query = query.filter(column == val)
                        elif op == 'ne':
                            query = query.filter(column != val)
                        elif op == 'gt':
                            query = query.filter(column > val)
                        elif op == 'gte':
                            query = query.filter(column >= val)
                        elif op == 'lt':
                            query = query.filter(column < val)
                        elif op == 'lte':
                            query = query.filter(column <= val)
                        elif op == 'in':
                            query = query.filter(column.in_(val))
                        elif op == 'like':
                            query = query.filter(column.like(f"%{val}%"))
                        elif op == 'ilike':
                            query = query.filter(column.ilike(f"%{val}%"))
                else:
                    # Simple equality filter
                    query = query.filter(column == value)
        
        return query
    
    def bulk_create(self, objects: List[Dict[str, Any]]) -> List[T]:
        """Bulk create records."""
        try:
            db_objects = [self.model(**obj) for obj in objects]
            self.db.add_all(db_objects)
            self.db.commit()
            
            for obj in db_objects:
                self.db.refresh(obj)
            
            self.logger.info(
                "Bulk create completed",
                model=self.model.__name__,
                count=len(db_objects)
            )
            
            return db_objects
            
        except Exception as exc:
            self.db.rollback()
            self.logger.error(
                "Error in bulk create",
                model=self.model.__name__,
                count=len(objects),
                error=str(exc),
                exc_info=True
            )
            raise
    
    def bulk_update(self, updates: List[Dict[str, Any]]) -> bool:
        """Bulk update records."""
        try:
            for update_data in updates:
                if 'id' not in update_data:
                    continue
                
                record_id = update_data.pop('id')
                self.db.query(self.model).filter(
                    self.model.id == record_id
                ).update(update_data)
            
            self.db.commit()
            
            self.logger.info(
                "Bulk update completed",
                model=self.model.__name__,
                count=len(updates)
            )
            
            return True
            
        except Exception as exc:
            self.db.rollback()
            self.logger.error(
                "Error in bulk update",
                model=self.model.__name__,
                count=len(updates),
                error=str(exc),
                exc_info=True
            )
            raise